8865bb
@@ -46,7 +46,8 @@
import org.springframework.batch.retry.support.DefaultRetryState;
  * allows for skipping or retry of items that cause exceptions during writing.
  * 
  */
-public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O> {
+public class FaultTolerantChunkProcessor<I, O> extends
+		SimpleChunkProcessor<I, O> {
 
 	private SkipPolicy itemProcessSkipPolicy = new LimitCheckingItemSkipPolicy();
 
@@ -54,7 +55,8 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 
 	private final BatchRetryTemplate batchRetryTemplate;
 
-	private Classifier<Throwable, Boolean> rollbackClassifier = new BinaryExceptionClassifier(true);
+	private Classifier<Throwable, Boolean> rollbackClassifier = new BinaryExceptionClassifier(
+			true);
 
 	private Log logger = LogFactory.getLog(getClass());
 
@@ -71,21 +73,24 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	 * Not used in the case of the {@link #setBuffering(boolean) buffering flag}
 	 * being true (the default).
 	 * 
-	 * @param keyGenerator the {@link KeyGenerator} to set
+	 * @param keyGenerator
+	 *            the {@link KeyGenerator} to set
 	 */
 	public void setKeyGenerator(KeyGenerator keyGenerator) {
 		this.keyGenerator = keyGenerator;
 	}
 
 	/**
-	 * @param SkipPolicy the {@link SkipPolicy} for item processing
+	 * @param SkipPolicy
+	 *            the {@link SkipPolicy} for item processing
 	 */
 	public void setProcessSkipPolicy(SkipPolicy SkipPolicy) {
 		this.itemProcessSkipPolicy = SkipPolicy;
 	}
 
 	/**
-	 * @param SkipPolicy the {@link SkipPolicy} for item writing
+	 * @param SkipPolicy
+	 *            the {@link SkipPolicy} for item writing
 	 */
 	public void setWriteSkipPolicy(SkipPolicy SkipPolicy) {
 		this.itemWriteSkipPolicy = SkipPolicy;
@@ -97,7 +102,8 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	 * 
 	 * @param rollbackClassifier
 	 */
-	public void setRollbackClassifier(Classifier<Throwable, Boolean> rollbackClassifier) {
+	public void setRollbackClassifier(
+			Classifier<Throwable, Boolean> rollbackClassifier) {
 		this.rollbackClassifier = rollbackClassifier;
 	}
 
@@ -125,14 +131,17 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	 * true). If false then the processor is only called once per item per
 	 * chunk, even if there are rollbacks with retries and skips.
 	 * 
-	 * @param processorTransactional the flag value to set
+	 * @param processorTransactional
+	 *            the flag value to set
 	 */
 	public void setProcessorTransactional(boolean processorTransactional) {
 		this.processorTransactional = processorTransactional;
 	}
 
-	public FaultTolerantChunkProcessor(ItemProcessor<? super I, ? extends O> itemProcessor,
-			ItemWriter<? super O> itemWriter, BatchRetryTemplate batchRetryTemplate) {
+	public FaultTolerantChunkProcessor(
+			ItemProcessor<? super I, ? extends O> itemProcessor,
+			ItemWriter<? super O> itemWriter,
+			BatchRetryTemplate batchRetryTemplate) {
 		super(itemProcessor, itemWriter);
 		this.batchRetryTemplate = batchRetryTemplate;
 	}
@@ -193,16 +202,19 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	}
 
 	@Override
-	protected Chunk<O> transform(final StepContribution contribution, Chunk<I> inputs) throws Exception {
+	protected Chunk<O> transform(final StepContribution contribution,
+			Chunk<I> inputs) throws Exception {
 
 		Chunk<O> outputs = new Chunk<O>();
 		@SuppressWarnings("unchecked")
 		final UserData<O> data = (UserData<O>) inputs.getUserData();
 		final Chunk<O> cache = data.getOutputs();
-		final Iterator<O> cacheIterator = cache.isEmpty() ? null : new ArrayList<O>(cache.getItems()).iterator();
+		final Iterator<O> cacheIterator = cache.isEmpty() ? null
+				: new ArrayList<O>(cache.getItems()).iterator();
 		final AtomicInteger count = new AtomicInteger(0);
 
-		for (final Chunk<I>.ChunkIterator iterator = inputs.iterator(); iterator.hasNext();) {
+		for (final Chunk<I>.ChunkIterator iterator = inputs.iterator(); iterator
+				.hasNext();) {
 
 			final int scanLimit = processorTransactional ? 1 : 0;
 			final I item = iterator.next();
@@ -213,7 +225,8 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 					O output = null;
 					try {
 						count.incrementAndGet();
-						O cached = (cacheIterator != null && cacheIterator.hasNext()) ? cacheIterator.next() : null;
+						O cached = (cacheIterator != null && cacheIterator
+								.hasNext()) ? cacheIterator.next() : null;
 						if (cached != null && count.get() > scanLimit) {
 							/*
 							 * If there is a cached chunk then we must be
@@ -223,30 +236,29 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 							 * cache.
 							 */
 							output = cached;
-						}
-						else {
+						} else {
 							output = doProcess(item);
 							if (!processorTransactional) {
 								cache.add(output);
 							}
 						}
-					}
-					catch (Exception e) {
+					} catch (Exception e) {
 						if (rollbackClassifier.classify(e)) {
 							// Default is to rollback unless the classifier
 							// allows us to continue
 							throw e;
-						}
-						else if (shouldSkip(itemProcessSkipPolicy, e, contribution.getStepSkipCount())) {
+						} else if (shouldSkip(itemProcessSkipPolicy, e,
+								contribution.getStepSkipCount())) {
 							// If we are not re-throwing then we should check if
 							// this is skippable
 							contribution.incrementProcessSkipCount();
-							logger.debug("Skipping after failed process with no rollback", e);
+							logger.debug(
+									"Skipping after failed process with no rollback",
+									e);
 							// If not re-throwing then the listener will not be
 							// called in next chunk.
 							callProcessSkipListener(item, e);
-						}
-						else {
+						} else {
 							// If it's not skippable that's an error in
 							// configuration - it doesn't make sense to not roll
 							// back if we are also not allowed to skip
@@ -269,17 +281,19 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 
 				public O recover(RetryContext context) throws Exception {
 					Throwable e = context.getLastThrowable();
-					if (shouldSkip(itemProcessSkipPolicy, e, contribution.getStepSkipCount())) {
+					if (shouldSkip(itemProcessSkipPolicy, e,
+							contribution.getStepSkipCount())) {
 						iterator.remove(e);
 						contribution.incrementProcessSkipCount();
 						logger.debug("Skipping after failed process", e);
 						return null;
-					}
-					else {
+					} else {
 						if (rollbackClassifier.classify(e)) {
 							// Default is to rollback unless the classifier
 							// allows us to continue
-							throw new RetryException("Non-skippable exception in recoverer while processing", e);
+							throw new RetryException(
+									"Non-skippable exception in recoverer while processing",
+									e);
 						}
 						iterator.remove(e);
 						return null;
@@ -288,8 +302,9 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 
 			};
 
-			O output = batchRetryTemplate.execute(retryCallback, recoveryCallback, new DefaultRetryState(
-					getInputKey(item), rollbackClassifier));
+			O output = batchRetryTemplate.execute(retryCallback,
+					recoveryCallback, new DefaultRetryState(getInputKey(item),
+							rollbackClassifier));
 			if (output != null) {
 				outputs.add(output);
 			}
@@ -301,8 +316,8 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	}
 
 	@Override
-	protected void write(final StepContribution contribution, final Chunk<I> inputs, final Chunk<O> outputs)
-			throws Exception {
+	protected void write(final StepContribution contribution,
+			final Chunk<I> inputs, final Chunk<O> outputs) throws Exception {
 
 		RetryCallback<Object> retryCallback = new RetryCallback<Object>() {
 			public Object doWithRetry(RetryContext context) throws Exception {
@@ -311,8 +326,7 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 					chunkMonitor.setChunkSize(inputs.size());
 					try {
 						doWrite(outputs.getItems());
-					}
-					catch (Exception e) {
+					} catch (Exception e) {
 						if (rollbackClassifier.classify(e)) {
 							throw e;
 						}
@@ -323,12 +337,12 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 						 * contract.
 						 */
 						throw new ForceRollbackForWriteSkipException(
-								"Force rollback on skippable exception so that skipped item can be located.", e);
+								"Force rollback on skippable exception so that skipped item can be located.",
+								e);
 					}
 					contribution.incrementWriteCount(outputs.size());
-				}
-				else {
-					scan(contribution, inputs, outputs, chunkMonitor);
+				} else {
+					scan(contribution, inputs, outputs, chunkMonitor, false);
 				}
 				return null;
 
@@ -343,17 +357,21 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 
 					Throwable e = context.getLastThrowable();
 					if (outputs.size() > 1 && !rollbackClassifier.classify(e)) {
-						throw new RetryException("Invalid retry state during write caused by "
-								+ "exception that does not classify for rollback: ", e);
+						throw new RetryException(
+								"Invalid retry state during write caused by "
+										+ "exception that does not classify for rollback: ",
+								e);
 					}
 
 					Chunk<I>.ChunkIterator inputIterator = inputs.iterator();
-					for (Chunk<O>.ChunkIterator outputIterator = outputs.iterator(); outputIterator.hasNext();) {
+					for (Chunk<O>.ChunkIterator outputIterator = outputs
+							.iterator(); outputIterator.hasNext();) {
 
 						inputIterator.next();
 						outputIterator.next();
 
-						checkSkipPolicy(inputIterator, outputIterator, e, contribution);
+						checkSkipPolicy(inputIterator, outputIterator, e,
+								contribution, true);
 						if (!rollbackClassifier.classify(e)) {
 							throw new RetryException(
 									"Invalid retry state during recovery caused by exception that does not classify for rollback: ",
@@ -369,10 +387,10 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 			};
 
 			batchRetryTemplate.execute(retryCallback, batchRecoveryCallback,
-					BatchRetryTemplate.createState(getInputKeys(inputs), rollbackClassifier));
+					BatchRetryTemplate.createState(getInputKeys(inputs),
+							rollbackClassifier));
 
-		}
-		else {
+		} else {
 
 			RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
 
@@ -383,14 +401,15 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 					 * do any scanning. We can just bomb out with a retry
 					 * exhausted.
 					 */
-					if (!shouldSkip(itemWriteSkipPolicy, context.getLastThrowable(), -1)) {
+					if (!shouldSkip(itemWriteSkipPolicy,
+							context.getLastThrowable(), -1)) {
 						throw new ExhaustedRetryException(
 								"Retry exhausted after last attempt in recovery path, but exception is not skippable.",
 								context.getLastThrowable());
 					}
 
 					inputs.setBusy(true);
-					scan(contribution, inputs, outputs, chunkMonitor);
+					scan(contribution, inputs, outputs, chunkMonitor, true);
 					return null;
 				}
 
@@ -399,8 +418,8 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 			if (logger.isDebugEnabled()) {
 				logger.debug("Attempting to write: " + inputs);
 			}
-			batchRetryTemplate.execute(retryCallback, recoveryCallback, new DefaultRetryState(inputs,
-					rollbackClassifier));
+			batchRetryTemplate.execute(retryCallback, recoveryCallback,
+					new DefaultRetryState(inputs, rollbackClassifier));
 
 		}
 
@@ -423,9 +442,9 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 			Throwable e = wrapper.getException();
 			try {
 				getListener().onSkipInWrite(wrapper.getItem(), e);
-			}
-			catch (RuntimeException ex) {
-				throw new SkipListenerFailedException("Fatal exception in SkipListener.", ex, e);
+			} catch (RuntimeException ex) {
+				throw new SkipListenerFailedException(
+						"Fatal exception in SkipListener.", ex, e);
 			}
 		}
 
@@ -439,15 +458,17 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	 * Convenience method for calling process skip listener, so that it can be
 	 * called from multiple places.
 	 * 
-	 * @param item the item that is skipped
-	 * @param e the cause of the skip
+	 * @param item
+	 *            the item that is skipped
+	 * @param e
+	 *            the cause of the skip
 	 */
 	private void callProcessSkipListener(I item, Throwable e) {
 		try {
 			getListener().onSkipInProcess(item, e);
-		}
-		catch (RuntimeException ex) {
-			throw new SkipListenerFailedException("Fatal exception in SkipListener.", ex, e);
+		} catch (RuntimeException ex) {
+			throw new SkipListenerFailedException(
+					"Fatal exception in SkipListener.", ex, e);
 		}
 	}
 
@@ -455,19 +476,21 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 	 * Convenience method for calling process skip policy, so that it can be
 	 * called from multiple places.
 	 * 
-	 * @param policy the skip policy
-	 * @param e the cause of the skip
-	 * @param skipCount the current skip count
+	 * @param policy
+	 *            the skip policy
+	 * @param e
+	 *            the cause of the skip
+	 * @param skipCount
+	 *            the current skip count
 	 */
 	private boolean shouldSkip(SkipPolicy policy, Throwable e, int skipCount) {
 		try {
 			return policy.shouldSkip(e, skipCount);
-		}
-		catch (SkipLimitExceededException ex) {
+		} catch (SkipLimitExceededException ex) {
 			throw ex;
-		}
-		catch (RuntimeException ex) {
-			throw new SkipListenerFailedException("Fatal exception in SkipPolicy.", ex, e);
+		} catch (RuntimeException ex) {
+			throw new SkipListenerFailedException(
+					"Fatal exception in SkipPolicy.", ex, e);
 		}
 	}
 
@@ -489,25 +512,44 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 		return keys;
 	}
 
-	private void checkSkipPolicy(Chunk<I>.ChunkIterator inputIterator, Chunk<O>.ChunkIterator outputIterator,
-			Throwable e, StepContribution contribution) {
+	private void checkSkipPolicy(Chunk<I>.ChunkIterator inputIterator,
+			Chunk<O>.ChunkIterator outputIterator, Throwable e,
+			StepContribution contribution, boolean recovery) throws Exception {
 		logger.debug("Checking skip policy after failed write");
 		if (shouldSkip(itemWriteSkipPolicy, e, contribution.getStepSkipCount())) {
 			contribution.incrementWriteSkipCount();
 			inputIterator.remove();
 			outputIterator.remove(e);
 			logger.debug("Skipping after failed write", e);
-		}
-		else {
-			throw new RetryException("Non-skippable exception in recoverer", e);
+		} else {
+			if (recovery) {
+				// Only if already recovering should we check skip policy
+				throw new RetryException(
+						"Non-skippable exception in recoverer", e);
+			} else {
+				if (e instanceof Exception) {
+					throw (Exception) e;
+				} else if (e instanceof Error) {
+					throw (Error) e;
+				} else {
+					throw new RetryException(
+							"Non-skippable throwable in recoverer", e);
+				}
+			}
 		}
 	}
 
-	private void scan(final StepContribution contribution, final Chunk<I> inputs, final Chunk<O> outputs,
-			ChunkMonitor chunkMonitor) throws Exception {
+	private void scan(final StepContribution contribution,
+			final Chunk<I> inputs, final Chunk<O> outputs,
+			ChunkMonitor chunkMonitor, boolean recovery) throws Exception {
 
 		if (logger.isDebugEnabled()) {
-			logger.debug("Scanning for failed item on write: " + inputs);
+			if (recovery) {
+				logger.debug("Scanning for failed item on recovery from write: "
+						+ inputs);
+			} else {
+				logger.debug("Scanning for failed item on write: " + inputs);
+			}
 		}
 		if (outputs.isEmpty()) {
 			inputs.setBusy(false);
@@ -527,15 +569,15 @@
public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
 			contribution.incrementWriteCount(1);
 			inputIterator.remove();
 			outputIterator.remove();
-		}
-		catch (Exception e) {
+		} catch (Exception e) {
 			doOnWriteError(e, items);
-			if (!shouldSkip(itemWriteSkipPolicy, e, -1) && !rollbackClassifier.classify(e)) {
+			if (!shouldSkip(itemWriteSkipPolicy, e, -1)
+					&& !rollbackClassifier.classify(e)) {
 				inputIterator.remove();
 				outputIterator.remove();
-			}
-			else {
-				checkSkipPolicy(inputIterator, outputIterator, e, contribution);
+			} else {
+				checkSkipPolicy(inputIterator, outputIterator, e, contribution,
+						recovery);
 			}
 			if (rollbackClassifier.classify(e)) {
 				throw e;
